package com.jxgyl.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import com.jxgyl.common.util.ErrorException;
import com.jxgyl.common.util.HtmlEncodeUtil;
import com.jxgyl.common.util.SqlEncodeUtil;
import com.jxgyl.gateway.config.GateWayConfig;
import lombok.extern.log4j.Log4j2;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @program: middleplatform
 * @description: 参数过滤器
 * @author: Wanglipeng
 * @create: 2022-01-24 09:57
 **/
@Component
@Log4j2
public class RequestParamFilter extends AbstractGatewayFilterFactory {

    @Resource
    GateWayConfig gateWayConfig;

    @Override
    public GatewayFilter apply(Object config) {
        return ((exchange, chain) -> {
            log.info("RequestParamFilter_test");

           // req
            ServerHttpRequest serverHttpRequest = exchange.getRequest();
            // 重新构造request，参考ModifyRequestBodyGatewayFilterFactory
            ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
            // 请求方式 --   //if (method == HttpMethod.POST || method == HttpMethod.PUT || method == HttpMethod.DELETE || method == HttpMethod.GET) {
            String method = serverHttpRequest.getMethodValue();
            // 请求参数类型
            MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
            log.info("mediaType="+mediaType);
            // 不对文件上传做处理
            if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(mediaType)) {
                return chain.filter(exchange);
            }
            //  json 格式参数传参
            if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType) || MediaType.APPLICATION_JSON_UTF8.isCompatibleWith(mediaType)) {
                Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
                    //验签，xss攻击等
                    String newBody = handleBody(body);
                    //临时保存数据
                    exchange.getResponse().getHeaders().add("bodyStr", newBody);
                    //返回数据
                    return Mono.just(newBody);
                    // return Mono.empty();
                });
                log.info("jsonbody="+modifiedBody);
                BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
                HttpHeaders headers = new HttpHeaders();
                headers.putAll(exchange.getRequest().getHeaders());
                //猜测这个就是之前报400错误的元凶，之前修改了body但是没有重新写content length
                headers.remove("Content-Length");
                //CachedBodyOutputMessage
                CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
                // return开始执行 serverRequest.bodyToMono方法
                return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
                    ServerHttpRequest decorator = this.decorate(exchange, headers, outputMessage);
                    return returnMono(chain, exchange.mutate().request(decorator).build());
                }));
            } else {
                //  非 json 格式参数传参，得到 queryParams 参数后，做你想做的事，可以处理sql 注入， xss 攻击处理等,java中URL 的编码和解码函数java.net.URLEncoder.encode(String s)和java.net.URLDecoder.decode(String s);
                MultiValueMap<String, String> queryParams = serverHttpRequest.getQueryParams();
                log.info("queryMap="+queryParams);
                URI uri = serverHttpRequest.getURI();
                //  重写uri参数，自定义handleParam 方法进行参数替换/过滤
                URI newUri = UriComponentsBuilder.fromUri(uri)
                        .replaceQuery(this.handleParam(queryParams))
                        .build(true)
                        .toUri();
                //下面的将请求体再次封装写回到request里，传到下一级，否则，由于请求体已被消费，后续的服务将取不到值
                ServerHttpRequest request = serverHttpRequest.mutate().uri(newUri).build();
                // 临时保存请求参数
                exchange.getResponse().getHeaders().add("queryParams", queryParams.toString());
                //封装request，传给下一级
                return chain.filter(exchange.mutate().request(request).build());
            }

        });
    }

    private Mono<Void> returnMono(GatewayFilterChain chain, ServerWebExchange exchange) {
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            Long startTime = exchange.getAttribute("startTime");
            if (startTime != null) {
                long executeTime = (System.currentTimeMillis() - startTime);
                log.info("耗时：{}ms", executeTime);
                log.info("状态码：{}", Objects.requireNonNull(exchange.getResponse().getStatusCode()).value());
            }
        }));
    }

    ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) {
        return new ServerHttpRequestDecorator(exchange.getRequest()) {
            public HttpHeaders getHeaders() {
                long contentLength = headers.getContentLength();
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                if (contentLength > 0L) {
                    httpHeaders.setContentLength(contentLength);
                } else {
                    httpHeaders.set("Transfer-Encoding", "chunked");
                }
                return httpHeaders;
            }

            public Flux<DataBuffer> getBody() {
                return outputMessage.getBody();
            }
        };
    }


    /**
     * TODO 处理params 参数
     *
     * @return 为url 字符串拼接内容，如： ?aldtype=16047&query=&keyfrom=baidu ，value值需 URLEncoder.encode
     * @author ws
     * @mail 1720696548@qq.com
     * @date 2020/2/13 0013 23:52
     */
    private String handleParam(MultiValueMap<String, String> queryParams) {
        // 处理get请求参数
        if (queryParams.size() <= 0) {
            return "";
        }
        log.info(gateWayConfig.isOpenSqlAttack());
        // sql 注入攻击处理
        if (gateWayConfig.isOpenSqlAttack()) {
            log.info("sql注入校验");
            boolean passSqlInjection = SqlEncodeUtil.isPassSqlInjection(queryParams.toString());
            if (!passSqlInjection) {
                throw new ErrorException(queryParams.toString() + "请求可能存在sql注入");
            }
        }
        // xss 攻击处理，去掉[ 和 ]
        String jsonQueryParams = JSONObject.toJSONString(queryParams);
        String newJsonQueryParams = jsonQueryParams.replaceAll("\\[", "").replaceAll("\\]", "");
        Map<String, String> mapParams = new HashMap<>();
        if (gateWayConfig.isOpenXss()) {
            log.info("xss校验");
            mapParams = JSONObject.parseObject(HtmlEncodeUtil.htmlEncode(newJsonQueryParams), Map.class);
        } else {
            mapParams = JSONObject.parseObject(newJsonQueryParams, Map.class);
        }
        StringBuilder query = new StringBuilder();
        for (String key : mapParams.keySet()) {
            // 对原有的每个参数进行操作
            query.append(key + "=" + java.net.URLEncoder.encode(mapParams.get(key)) + "&");
        }
        return query.toString().substring(0, query.length() - 1);
    }

    private String handleBody(String bodyStr) {
        if (bodyStr.isEmpty()) {
            return "";
        }
        // sql 注入攻击处理
        if (gateWayConfig.isOpenSqlAttack()) {
            log.info("sql注入校验1");
            boolean passSqlInjection = SqlEncodeUtil.isPassSqlInjection(bodyStr);
            if (!passSqlInjection) {
                throw new ErrorException("请求可能存在sql注入");
            }
        }
        // xss攻击处理
        if (gateWayConfig.isOpenXss()) {
            return HtmlEncodeUtil.htmlEncode(bodyStr);
        } else {
            return bodyStr;
        }
    }

}
